/**
 * @file IxOsalOsServices.c (l4aos)
 *
 * @brief Implementation for Irq, Mem, sleep. 
 * 
 * 
 * @par
 * IXP400 SW Release version 2.1
 * 
 * -- Copyright Notice --
 * 
 * @par
 * Copyright (c) 2001-2005, Intel Corporation.
 * All rights reserved.
 * 
 * @par
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * 
 * @par
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 * 
 * @par
 * -- End of Copyright Notice --
 */

#include "ixp_osal/IxOsal.h"

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

#include <sel4/sel4.h>

#include "ixp_osal/os/ixp400/IxOsalOsIxp425Irq.h"




#define verbose 0

#define dprintf(v, ...) do { if(v < verbose) printf(__VA_ARGS__);}while(0)

/*********************************************************************************
 *** External services provided by OS
 *** Since the OS is constantly evolving, we assume generic functions will be
 *** provided within the OS such that this library may remain consistant
 ********************************************************************************/

/** 
 * map device region.
 * @param paddr start address of device region
 * @param vaddr the virtual address to map to TODO must this be fixed?
 * @return the number of frames mapped
 */
extern int OsalMapDevice(seL4_Word paddr, seL4_Word vaddr);

/**
 * unmap device region.
 * @param paddr start address of device region
 * @param vaddr the virtual address that the device was mapped to
 * @return the number of frames unmapped
 */
extern int OsalUnmapDevice(seL4_Word paddr, seL4_Word vaddr);

/**
 * create an IRQ handler
 * @param irq the irq number/vector to register
 * @return the IRQ hander created 
 */
extern seL4_IRQHandler OsalNewIRQ(int irq);

/**
 * log information in printf style
 */
void _logf(const char *msg, ...);

/********************************************************************************/




/* by default trace all but debug message */
//static IxOsalLogLevel sIxOsalCurrLogLevel = IX_OSAL_LOG_LVL_MESSAGE;
static IxOsalLogLevel sIxOsalCurrLogLevel = IX_OSAL_LOG_LVL_ALL;

typedef struct IxOsalIRQEntry {
    void (*fRoutine)(void *);
    void *fParameter;
    uint32_t fFlags;
    seL4_IRQControl irq_handler;
} IxOsalIRQEntry;

enum IxOsalIRQEntryFlags {
    kIxOsalIntOccurred = 0x00000001, kIxOsalIntEnabled = 0x00000002, kIxOsalIntActive = (kIxOsalIntOccurred
            | kIxOsalIntEnabled), kIxOsalIntReserved = 0x00000004,
};

typedef struct frame_list {
    seL4_Word frame;
    struct frame_list *next;
} frame_list_t;

#define MAX_INTERRUPT seL4_BadgeBits

#define NR_INTS 32
static IxOsalIRQEntry sIxOsalIrqInfo[NR_INTS];

static bool sIxOsalOsServicesInited;
static int lockCount;
static IxOsalMutex irqLock;
static IxOsalMutex irqBlock;
static seL4_Word rootThreadId;

// Only used from the IxOsalOsIxp400.c:ixOsalOemInit() routine
extern void ixOsalOSServicesInit(void);
extern void sos_usleep(int usecs);

void ixOsalOSServicesInit(void) {
    if (sIxOsalOsServicesInited)
        return;

    sIxOsalOsServicesInited = true;

    assert(ixOsalMutexInitLib() == IX_SUCCESS);

    assert(ixOsalMutexInit(&irqLock) == IX_SUCCESS);
    assert(ixOsalMutexInit(&irqBlock) == IX_SUCCESS);

    lockCount = 0;
    // Assumption that every thread has a unique IPC buffer
    rootThreadId = (seL4_Word) seL4_GetIPCBuffer();
}

extern void ixOsalOSServicesFinaliseInit(void);
void ixOsalOSServicesFinaliseInit(void) {
    // Block out timer1 & timestamp interrupts
    //    int i = sizeof(sIxOsalIrqInfo) / sizeof(sIxOsalIrqInfo[0]);
    //    do {
    //        if (!sIxOsalIrqInfo[--i].fRoutine)
    //            sIxOsalIrqInfo[i].fFlags = kIxOsalIntReserved;
    //    } while (i);
}

/*
 * General interrupt handler
 */
extern void ixOsalOSServicesServiceInterrupt(seL4_Word badge);
void ixOsalOSServicesServiceInterrupt(seL4_Word badge) {

    int i;

    ixOsalMutexLock(&irqBlock, IX_OSAL_WAIT_FOREVER);
    rootThreadId = (seL4_Word) seL4_GetIPCBuffer();

    //iterate through all of the possible interrupts. It's only 32.
    for (i = 0; i < seL4_BadgeBits; i++) {
        if (badge & (1 << i)) {
            int irq_number = i;
            IxOsalIRQEntry *irq = &sIxOsalIrqInfo[irq_number];

            //only reply to interrupts that we have registered callbacks for.
            if (irq->fRoutine) {
                dprintf(1, "%s: calling service routine\n", __func__);
                (*irq->fRoutine)(irq->fParameter);

                seL4_IRQHandler_Ack(irq->irq_handler);
            } else {
            	dprintf(1, "%s: ignoring interrupt number %d\n", __FUNCTION__, irq_number);
            }
        }
    }

    ixOsalMutexUnlock(&irqBlock);

}

/**************************************
 * Irq services 
 *************************************/


PUBLIC IX_STATUS
ixOsalIrqBind(UINT32 vector, IxOsalVoidFnVoidPtr routine, void *parameter) {
    assert(sIxOsalOsServicesInited);
    ixOsalMutexLock(&irqLock, IX_OSAL_WAIT_FOREVER);
    ixOsalMutexLock(&irqBlock, IX_OSAL_WAIT_FOREVER);
    IxOsalIRQEntry *irq = &sIxOsalIrqInfo[vector];
    assert(!(irq->fFlags & kIxOsalIntReserved));

    if (vector >= NR_INTS || irq->fRoutine) {
        ixOsalLog(IX_OSAL_LOG_LVL_ERROR, IX_OSAL_LOG_DEV_STDOUT, "%s: illegal %d %p.\n", LOG_FUNCTION, vector,
                (uintptr_t) irq->fRoutine, 0, 0, 0);
        return IX_FAIL;
    }

    irq->fRoutine = routine;
    irq->fParameter = parameter;
    irq->fFlags = kIxOsalIntEnabled;

    seL4_IRQHandler interrupt_handler = OsalNewIRQ(vector);

    if (!interrupt_handler) {
        _logf("%s: registering IRQ %lu failed! error=%lu\n", __FUNCTION__, (unsigned long) vector, (unsigned long) seL4_GetMR(0));
    }
    seL4_IRQHandler_Ack(interrupt_handler);
    irq->irq_handler = interrupt_handler;

    dprintf(1, "bound interrupt on vector %d\n", vector);

    ixOsalMutexUnlock(&irqBlock);
    ixOsalMutexUnlock(&irqLock);
    return IX_SUCCESS;
}

PUBLIC IX_STATUS
ixOsalIrqUnbind(UINT32 vector) {
    assert(sIxOsalOsServicesInited);
    assert(vector >= 0 && vector < seL4_BadgeBits);
    ixOsalMutexLock(&irqLock, IX_OSAL_WAIT_FOREVER);

    IxOsalIRQEntry *irq = &sIxOsalIrqInfo[vector];

    int error = seL4_IRQHandler_Clear(irq->irq_handler);
    irq->fRoutine = NULL;
    irq->fParameter = NULL;
    irq->fFlags = 0;

    ixOsalMutexUnlock(&irqLock);

    return error;
}

PUBLIC UINT32
ixOsalIrqLock() {
    assert(sIxOsalOsServicesInited);
    ixOsalMutexLock(&irqLock, IX_OSAL_WAIT_FOREVER);
    UINT32 result = (lockCount != 0);
    if (((seL4_Word) seL4_GetIPCBuffer()) != rootThreadId) {
        if (lockCount == 0) {
            ixOsalMutexLock(&irqBlock, IX_OSAL_WAIT_FOREVER);
        }
    }
    lockCount++;
    ixOsalMutexUnlock(&irqLock);
    return result;
}

/* Enable interrupts and task scheduling,
 * input parameter: irqEnable status returned
 * by ixOsalIrqLock().
 */
PUBLIC void ixOsalIrqUnlock(UINT32 lockKey) {
    assert(sIxOsalOsServicesInited);
    ixOsalMutexLock(&irqLock, IX_OSAL_WAIT_FOREVER);
    lockCount--;
    if (((seL4_Word) seL4_GetIPCBuffer()) != rootThreadId) {
        if (lockCount == 0) {
            ixOsalMutexUnlock(&irqBlock);
        }
    }
    ixOsalMutexUnlock(&irqLock);
}

// Not a supported function
PUBLIC UINT32
ixOsalIrqLevelSet(UINT32 level) {
    printf("WARNING: %s called\n", __FUNCTION__);
    assert(!"ixOsalIrqLevelSet");
    return 0;
}

PUBLIC void ixOsalIrqEnable(UINT32 irqLevel) {
    assert(sIxOsalOsServicesInited);
    assert(!"ixOsalIrqEnable");
}

PUBLIC void ixOsalIrqDisable(UINT32 irqLevel) {
    assert(sIxOsalOsServicesInited);
    assert(!"ixOsalIrqDisable");
}

/*********************
 * Log function
 *********************/

INT32 ixOsalLog(IxOsalLogLevel level, IxOsalLogDevice device, char *format, int arg1, int arg2, int arg3, int arg4,
        int arg5, int arg6) {
    INT32 ret = IX_OSAL_LOG_ERROR;
    /*    if (!sIxOsalLogLock)
     ixOsalOSServicesInit();

     ixOsalSemaphoreWait(&sIxOsalLogLock, IX_OSAL_WAIT_FOREVER);
     do {
     // Return -1 for custom display devices
     if (device != IX_OSAL_LOG_DEV_STDOUT
     &&  device != IX_OSAL_LOG_DEV_STDERR) {
     printf("%s: only IX_OSAL_LOG_DEV_STDOUT and "
     "IX_OSAL_LOG_DEV_STDERR are supported\n", __FUNCTION__);
     break;
     }

     if (level <= sIxOsalCurrLogLevel && level != IX_OSAL_LOG_LVL_NONE) {
     if (level == IX_OSAL_LOG_LVL_USER)
     ret = printf("%lx", L4_ThreadNo(sos_my_tid()));
     else
     ret = printf(traceHeaders[level - 1], L4_ThreadNo(sos_my_tid()));

     ret += printf(format, arg1, arg2, arg3, arg4, arg5, arg6);
     break;
     }
     } while(0);
     ixOsalSemaphorePost(&sIxOsalLogLock);*/

    return ret;
}

PUBLIC UINT32
ixOsalLogLevelSet(UINT32 level) {
    UINT32 oldLevel;

    // Check value first
    if (IX_OSAL_LOG_LVL_NONE <= level && level <= IX_OSAL_LOG_LVL_ALL) {
        oldLevel = sIxOsalCurrLogLevel;
        sIxOsalCurrLogLevel = level;
        return oldLevel;
    } else {
        ixOsalLog(IX_OSAL_LOG_LVL_MESSAGE, IX_OSAL_LOG_DEV_STDOUT,
                "ixOsalLogLevelSet: Log Level is between %d and %d \n", IX_OSAL_LOG_LVL_NONE, IX_OSAL_LOG_LVL_ALL, 0,
                0, 0, 0);
        return IX_OSAL_LOG_LVL_NONE;
    }
}

/**************************************
 * Task services 
 *************************************/

PUBLIC void ixOsalBusySleep(UINT32 microseconds) {
    // never busy sleep on an L4 system
    /*    assert(microseconds < 32 * 1000 * 1000);
     sos_usleep(microseconds);*/
    sos_usleep(microseconds);
}

extern void sos_usleep(int usecs);

PUBLIC void ixOsalSleep(UINT32 milliseconds) {
    assert(milliseconds < 32 * 1000);
    sos_usleep(milliseconds * 1000);
}

/**************************************
 * Memory functions 
 *************************************/

void *
ixOsalMemAlloc(UINT32 size) {
    assert(!"ixOsalMemAlloc");
    return NULL;
}

void ixOsalMemFree(void *ptr) {
    assert(!"ixOsalMemFree");
}

/* 
 * Copy count bytes from src to dest ,
 * returns pointer to the dest mem zone.
 */
void *
ixOsalMemCopy(void *dest, void *src, UINT32 count) {
    IX_OSAL_ASSERT(dest);
    IX_OSAL_ASSERT(src);
    return memcpy(dest, src, count);
}

/* 
 * Fills a memory zone with a given constant byte,
 * returns pointer to the memory zone.
 */
void *
ixOsalMemSet(void *ptr, UINT8 filler, UINT32 count) {
    IX_OSAL_ASSERT(ptr);
    return memset(ptr, filler, count);
}

/*****************************
 *
 *  Time
 *
 *****************************/

/* Retrieve current system time */
void ixOsalTimeGet(IxOsalTimeval * ptime) {
    assert(!"ixOsalTimeGet");
}

/* Timestamp is implemented in OEM */
PUBLIC UINT32
ixOsalTimestampGet(void) {
    return IX_OSAL_OEM_TIMESTAMP_GET();
}

/* OEM-specific implementation */
PUBLIC UINT32
ixOsalTimestampResolutionGet(void) {
    return IX_OSAL_OEM_TIMESTAMP_RESOLUTION_GET();
}

PUBLIC UINT32
ixOsalSysClockRateGet(void) {
    return IX_OSAL_OEM_SYS_CLOCK_RATE_GET();
}

PUBLIC void ixOsalYield(void) {
    seL4_Yield();
}

/*****************************
 *
 *  Memory mapping functions
 *
 *****************************/

void IxOsalOsMapMemory(IxOsalMemoryMap *map) {
    seL4_Word paddr;
    seL4_Word vaddr;
    int pages_to_map;

    map->virtualAddress = IX_OSAL_IXP400_VIRT_OFFSET(map->physicalAddress);

    paddr = map->physicalAddress;
    vaddr = map->virtualAddress;

    pages_to_map = (map->size >> seL4_PageBits) + (map->size & ((1 << seL4_PageBits) - 1)? 1 : 0);
    printf("Device memory at %p to %p\n", (void *) vaddr, (void *) (vaddr + (pages_to_map << seL4_PageBits)));

    ixOsalLog(IX_OSAL_LOG_LVL_DEBUG1, IX_OSAL_LOG_DEV_STDOUT, "%s: Mapping(%lx,%lx) => %lx\n", LOG_FUNCTION,
            map->physicalAddress, map->size, vaddr, 0, 0);

    int n = OsalMapDevice(map->physicalAddress, map->virtualAddress);
    pages_to_map -= n;
    assert(pages_to_map == 0); 
}

void IxOsalOsUnmapMemory(IxOsalMemoryMap *map) {
    OsalUnmapDevice(map->physicalAddress, map->virtualAddress);
}

